<?php

namespace AppBundle\Util\Crypt;

use AppBundle\Log\LoggerTrait;
use AppBundle\Exception\CryptException;

/**
 * Description of CryptUtil
 *
 * @author yihao
 */
class CryptUtil
{
    
    use LoggerTrait;
    
    private static $gKey = 'CGB';
    
    private static $opensslKey = '018eb29f5c8fa1d1';
    private static $opensslMethod = 'AES-256-CBC';
    
    private $gExecPath;
    private $gSm2ExecPath;
    private $gSm2KeygenExecPath;
    private $gSm4ExecPath;
    private $gLibPath;
    private $gSmLibPath;
    private $gSm2PubKey;
    private $gSm2PriKey;
    
    public function __construct(
            $gExecPath, $gSm2ExecPath, $gSm2KeygenExecPath, $gSm4ExecPath,
            $gLibPath, $gSmLibPath,
            $gSm2PubKey, $gSm2PriKey)
    {
        $this->gExecPath = $gExecPath;
        $this->gSm2ExecPath = $gSm2ExecPath;
        $this->gSm2KeygenExecPath = $gSm2KeygenExecPath;
        $this->gSm4ExecPath = $gSm4ExecPath;
        $this->gLibPath = $gLibPath;
        $this->gSmLibPath = $gSmLibPath;
        $this->gSm2PubKey = $gSm2PubKey;
        $this->gSm2PriKey = $gSm2PriKey;
    }
    
    private function gCommandGenerator($type, $input, $key)
    {
        if (strripos($type, 'encrypt') !== false) {
            $operator = 'e';
        } else if (strripos($type, 'decrypt') !== false) {
            $operator = 'd';
        } else {
            throw new CryptException('Unsupperted crypt type: ' . $type);
        }
        
        return "$this->gExecPath $operator $this->gLibPath \"$input\" $key";
    }
    
    public function gEncrypt($msg, $key = null)
    {
        if ($key === null) {
            $key = static::$gKey;
        }
        
        $command = $this->gCommandGenerator(__FUNCTION__, $msg, $key);
        
        $this->logInfo('[Crypt|GEncrypt] Trying to exec command for g encrypt.');
        
        $output = [];
        $return = 0;
        exec($command, $output, $return);
        
        if ($return != 0 || !isset($output[0])) {
            $this->logError('[Crypt|GEncrypt] Exec command failed: ' . $return, $output);
            
            throw new CryptException('G encrypt error: ' . $return);
        }
        
        $this->logInfo('[Crypt|GEncrypt] Exec command success.');
        
        return $output[0];
    }
    
    public function gDecrypt($enc, $key = null)
    {
        if ($key === null) {
            $key = static::$gKey;
        }
        
        $command = $this->gCommandGenerator(__FUNCTION__, strtolower($enc), $key);
        
        $this->logInfo('[Crypt|GDecrypt] Trying to exec command for g decrypt.');
        
        $output = [];
        $return = 0;
        exec($command, $output, $return);
        
        if ($return != 0 || !isset($output[0])) {
            $this->logError('[Crypt|GDecrypt] Exec command failed: ' . $return, $output);
            
            throw new CryptException('G decrypt error: ' . $return);
        }
        
        $this->logInfo('[Crypt|GDecrypt] Exec command success.');
        
        return $output[0];
    }
    
    public static function encrypt($msg, $key = null)
    {
        $result = @openssl_encrypt($msg, static::$opensslMethod, $key ?: static::$opensslKey);
        
        return base64_encode($result);
    }
    
    public static function decrypt($enc, $key = null)
    {
        $msgEnc = base64_decode($enc);
        
        $result = @openssl_decrypt($msgEnc, static::$opensslMethod, $key ?: static::$opensslKey);
        
        return $result;
    }
    
    private function gSm2CommandGenerator($type, $input)
    {
        if (strripos($type, 'encrypt') !== false) {
            $operator = 'e';
            $key = CryptUtil::decrypt($this->gSm2PubKey);
        } else if (strripos($type, 'decrypt') !== false) {
            $operator = 'd';
            $key = CryptUtil::decrypt($this->gSm2PriKey);
        } else {
            throw new CryptException('Unsupperted crypt type: ' . $type);
        }
        
        return "$this->gSm2ExecPath $operator $this->gSmLibPath \"$input\" $key";
    }
    
    public function gSm2Encrypt($msg)
    {
        $command = $this->gSm2CommandGenerator(__FUNCTION__, $msg);
        
        $this->logInfo('[Crypt|GSm2Encrypt] Trying to exec command for g sm2 encrypt.');
        
        $output = [];
        $return = 0;
        exec($command, $output, $return);
        
        if ($return != 0 || !isset($output[0])) {
            $this->logError('[Crypt|GSm2Encrypt] Exec command failed: ' . $return, $output);
            
            throw new CryptException('G sm2 encrypt error: ' . $return);
        }
        
        $this->logInfo('[Crypt|GSm2Encrypt] Exec command success.');
        
        return $output[0];
    }
    
    public function gSm2Decrypt($enc)
    {
        $command = $this->gSm2CommandGenerator(__FUNCTION__, strtolower($enc));
        
        $this->logInfo('[Crypt|GSm2Decrypt] Trying to exec command for g sm2 decrypt.');
        
        $output = [];
        $return = 0;
        exec($command, $output, $return);
        
        if ($return != 0 || !isset($output[0])) {
            $this->logError('[Crypt|GSm2Decrypt] Exec command failed: ' . $return, $output);
            
            throw new CryptException('G sm2 decrypt error: ' . $return);
        }
        
        $this->logInfo('[Crypt|GSm2Decrypt] Exec command success.');
        
        return $output[0];
    }
    
    private function gSm4CommandGenerator($type, $input, $key)
    {
        if (strripos($type, 'encrypt') !== false) {
            $operator = 'e';
        } else if (strripos($type, 'decrypt') !== false) {
            $operator = 'd';
        } else {
            throw new CryptException('Unsupperted crypt type: ' . $type);
        }
        
        return "$this->gSm4ExecPath $operator $this->gSmLibPath \"$input\" $key";
    }
    
    public function gSm4Encrypt($msg, $key)
    {
        $command = $this->gSm4CommandGenerator(__FUNCTION__, $msg, $key);
        
        $this->logInfo('[Crypt|GSm4Encrypt] Trying to exec command for g sm4 encrypt.');
        
        $output = [];
        $return = 0;
        exec($command, $output, $return);
        
        if ($return != 0 || !isset($output[0])) {
            $this->logError('[Crypt|GSm4Encrypt] Exec command failed: ' . $return, $output);
            
            throw new CryptException('G sm4 encrypt error: ' . $return);
        }
        
        $this->logInfo('[Crypt|GSm4Encrypt] Exec command success.');
        
        return $output[0];
    }
    
    public function gSm4Decrypt($enc, $key)
    {
        $command = $this->gSm4CommandGenerator(__FUNCTION__, strtolower($enc), $key);
        
        $this->logInfo('[Crypt|GSm4Decrypt] Trying to exec command for g sm4 decrypt.');
        
        $output = [];
        $return = 0;
        exec($command, $output, $return);
        
        if ($return != 0 || !isset($output[0])) {
            $this->logError('[Crypt|GSm4Decrypt] Exec command failed: ' . $return, $output);
            
            throw new CryptException('G sm4 decrypt error: ' . $return);
        }
        
        $this->logInfo('[Crypt|GSm4Decrypt] Exec command success.');
        
        return $output[0];
    }
    
    public function gSm2Keygen()
    {
        $command = "$this->gSm2KeygenExecPath $this->gSmLibPath";
        
        $this->logInfo('[Crypt|GSm2Keygen] Trying to exec command for g sm2 keygen.');
        
        $output = [];
        $return = 0;
        exec($command, $output, $return);
        
        if ($return != 0 || !isset($output[0]) || !isset($output[1])) {
            $this->logError('[Crypt|GSm2Keygen] Exec command failed: ' . $return, $output);
            
            throw new CryptException('G sm2 keygen error: ' . $return);
        }
        
        $this->logInfo('[Crypt|GSm2Keygen] Exec command success.');
        
        return array(
            'pub' => $output[1],
            'pri' => $output[0],
        );
    }
    
}
